package engine;

import model.Model;
import java.util.Random;
import javafx.geometry.Point2D;

/**
 * The base agent class.
 *
 * <p>Cells operate in continuous space, it contains real coordinates. They
 * generally also use the provided state machine framework to interact and
 * respond to stimulus.</p>
 *
 * @author Ricardo Cruz {@literal <ricardo.pdm.cruz@gmail.com>}
 * @since  2014-11-11
 */
public abstract class Cell extends Statemachine
{
	public static final Parameter<Integer> r =
		new Parameter<Integer>(-1, "global", "cell-radius-um", 4);
/*	private static final Parameter<Integer> V =
		new Parameter<Integer>(-1, "cell", "volume-um2", 50);*/
	private static final Parameter<Double> l =
		new Parameter<Double>(-1, "global", "cell-diffuse-per-min", 2.);
	private static final Parameter<Integer> Rtot =
		new Parameter<Integer>(-1, "cell", "total-receptor-molecules", 50000);
	private static final Parameter<Double> Kr =
		new Parameter<Double>(-1, "cell", "recycle-rate-receptors-per-min", 0.5);
	private static final Parameter<Double> Kb =
		new Parameter<Double>(-1, "cell", "association-rate-per-min", 2e-6);
	private static final Parameter<Double> Ki =
		new Parameter<Double>(-1, "cell", "internalization-rate-per-min", 0.3);
	private static final Parameter<Double> k =
		new Parameter<Double>(-1, "cell", "cell-diffuse-responsiveness", 25.);

	@SuppressWarnings("rawtypes")
	protected Cell(Cell parent, StatemachineModel model, State initial) {
		super(model, initial);
	}

	@SuppressWarnings("rawtypes")
	protected Cell(double x, double y, StatemachineModel model, State initial) {
		super(model, initial);
		this.x = x;
		this.y = y;
	}

	protected double x, y;

	/**
	 * The X real coordinate.
	 * @return the x coordinate
	 */
	public double getX() { return x; }

	/**
	 * The Y real coordinate.
	 * @return the y coordinate
	 */
	public double getY() { return y; }

	/**
	 * The type of the cell; can be used with {@link Model}.
	 * @return type of the cell
	 */
	public abstract int getType();

	//** receptors in the cell
	// TODO: in the future we may want this in another class to allow for
	// multiple receptor types
	private double R = Rtot.get(), LR, Rstar;

	/**
	 * The local concentration of molecules is calculated by bilinear
	 * interpolation.
	 * @return the local concentration of molecules
	 */
	public double getLocalConcentration() {
		LayerFluids fluids = Simulation.get().getFluidsLayer();
		final int dL = LayerFluids.dL.get();
		int i = (int)(getX()/dL);
		int j = (int)(getY()/dL);
		double dx = getX() - i*dL;
		double dy = getY() - j*dL;
		return (1./(dL*dL*dL*dL)) * (
			  fluids.getAmountIndex(i  ,j  )*(dL-dx)*(dL-dy)
			+ fluids.getAmountIndex(i+1,j  )*(   dx)*(dL-dy)
			+ fluids.getAmountIndex(i  ,j+1)*(dL-dx)*(   dy)
			+ fluids.getAmountIndex(i+1,j+1)*(   dx)*(   dy));
	}

	/**
	 * Gradient at current position.
	 * @return gradient vector
	 */
	public Point2D getLocalGradient() {
		LayerFluids fluids = Simulation.get().getFluidsLayer();
		final int dL = LayerFluids.dL.get();
		int i = (int)(getX()/dL);
		int j = (int)(getY()/dL);
		double Q = fluids.getAmountIndex(i,j);
		double dx = getX() - i*dL;
		double dy = getY() - j*dL;
		return new Point2D(
			(1./(dL*dL*dL*dL)) * (
				(fluids.getAmountIndex(i+1,j)-Q)*(dL-dy)
				+(fluids.getAmountIndex(i+1,j+1)-fluids.getAmountIndex(i,j+1))*dy),
			(1./(dL*dL*dL*dL)) * (
				(fluids.getAmountIndex(i,j+1)-Q)*(dL-dx)
				+(fluids.getAmountIndex(i+1,j+1)-fluids.getAmountIndex(i+1,j))*dx));
	}


	/**
	 * Moves cell. Also handles the receptor-ligand interactions.
	 */
	public void diffuse() {
		// TODO: the article gives an idea of how to speed this up by varying dt
		final double dt = 1;  // in minutes

		//** receptor kinetics

		// local properties
		final double C = getLocalConcentration();
		final Point2D G = getLocalGradient();
		final double L = C;//*V.get();  // FIXME: not sure

		// receptor-ligand binding
		double dL = -Kb.get()*L*R;
		double dR = -Kb.get()*L*R + Kr.get()*Rstar;
		// receptor internalization and ligand consumption
		double dLR = Kb.get()*L*R - Ki.get()*LR;
		// receptor recycling
		double dRstar = Ki.get()*LR - Kr.get()*Rstar;

		// update quantities
		Simulation.get().getFluidsLayer().secrete(getX(),  getY(), dL);
		R += dR;
		LR += dLR;
		Rstar += dRstar;

		//** diffusion

		double Lfront = (C + G.magnitude()*r.get())/2; /* *V */
		double Lrear  = (C - G.magnitude()*r.get())/2; /* *V */
		double dLRdiff = Kb.get()*(Lfront-Lrear)*R*dt/2;
		Point2D dp_chemo = G.normalize().multiply(k.get()*(dLRdiff/dt));

		// randomness
		Random random = new Random();
		double theta = random.nextDouble() * 2*Math.PI;
		double m = random.nextDouble() * Cell.l.get() * dt;
		double dx_random = Math.cos(theta) * m;
		double dy_random = Math.sin(theta) * m;

		x += dx_random + dp_chemo.getX();
		y += dy_random + dp_chemo.getY();
	}
}
